﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Media.Imaging;

namespace GameEngine
{
    /// <remarks>
    ///     Provides methods to start, stop the game loop and change the gameboards.
    /// </remarks>
    public class Engine
    {
        private enum EngineState
        {
            running,
            stopped
        }

        /// <summary>
        ///     Source for the bitmap representing the visual. 
        /// </summary>
        private Image _screen;
        /// <summary>
        ///     The active gameboard.
        /// </summary>
        private GameGrid _active = null;
        /// <summary>
        ///     Current state of the engine, default is stopped.
        /// </summary>
        private EngineState _state = EngineState.stopped;
        private TimeSpan _previousTime;
        private TimeSpan _currentTime;
        private TimeSpan _elapsedTime;

        private bool _renderingEventHooked = false;


        /// <summary>
        ///     Sets the left property of the engine.
        /// </summary>
        public double Left
        {
            set
            {
                this._screen.SetValue(Canvas.LeftProperty, value);
            }
        }

        /// <summary>
        ///     Sets the top property of the engine.
        /// </summary>
        public double Top
        {
            set
            {
                this._screen.SetValue(Canvas.TopProperty, value);
            }
        }

        /// <summary>
        ///     Keeps track of the left mouse button state. False=Up, True=Down.
        /// </summary>
        private bool _leftMouseButtonState = false;
        /// <summary>
        ///     Caches the mouse moves so we don't have to recalculate paths too often.
        /// </summary>
        private IntPoint _mouseMoveCache = new IntPoint(0,0);


        public Engine(Image visual)
        {
            _screen = visual;
            //creates and sets the bitmap cache
            var cache = new BitmapCache();
            _screen.CacheMode = cache;

            _state = EngineState.stopped;

            //(GameEngine.App.Current.RootVisual as MainPage).LayoutRoot.Children.Add(_screen);

            GameEngine.App.Current.RootVisual.LostFocus += new RoutedEventHandler(RootVisual_LostFocus);
            GameEngine.App.Current.RootVisual.GotFocus += new RoutedEventHandler(RootVisual_GotFocus);
            CompositionTarget.Rendering += new EventHandler(GameLoop);
            _renderingEventHooked = true;
        }


        /// <summary>
        ///     Starts the gameloop.
        /// </summary>
        public void Run()
        {
            _state = EngineState.running;
        }


        /// <summary>
        ///     Stops the gameloop.
        /// </summary>
        public void Stop()
        {
            _state = EngineState.stopped;
        }


        /// <summary>
        ///     The gameloop. Handles pretty much everything.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GameLoop(object sender, EventArgs e)
        {

            if (_state == EngineState.running)
            {
                this._previousTime = this._currentTime;
                this._currentTime = ((RenderingEventArgs)e).RenderingTime;

                //calculates the time elapsed from the previous event
                this._elapsedTime = this._currentTime.Subtract(this._previousTime);

                if (_active == null)
                    _screen.Source = null;
                else
                {
                    //AI should be computed before
                    _active.ComputeAI(this._elapsedTime);

                    //moves gameobjects-handles position changes
                    _active.Move(this._elapsedTime);

                    //moves gameobjects-handles animation changes
                    _active.Animate(this._elapsedTime);

                    //updates the weather effect
                    _active.UpdateWeather(this._elapsedTime);

                    //renders the result
                    _screen.Source = _active.Update();
                }
            }
            else
            {
                //still keeps track of time so it does not skip after resume
                this._previousTime = this._currentTime;
                this._currentTime = ((RenderingEventArgs)e).RenderingTime; 
            }
        }
        

        /// <summary>
        ///     Attaches a gameboard(gamegrid/level) to the engine and makes it active.
        ///     Meaning it gets rendered, receives input... 
        /// </summary>
        /// <param name="gamegrid">Gameboard to be attached to the engine.</param>
        /// <exception cref="ArgumentNullException">Gets throwed when gamegrid is null.</exception>
        public void AttachGrid(GameGrid gamegrid)
        {
            if (gamegrid == null)
                throw new System.ArgumentNullException();

            //adds the gamegrid as active
            _active = gamegrid;

            //hooks the mouse events
            //_screen.MouseEnter += new MouseEventHandler(_screen_MouseEnter);
            //_screen.MouseLeave += new MouseEventHandler(_screen_MouseLeave);
            _screen.MouseLeftButtonDown += new MouseButtonEventHandler(_screen_MouseLeftButtonDown);
            _screen.MouseLeftButtonUp += new MouseButtonEventHandler(_screen_MouseLeftButtonUp);
            _screen.MouseMove += new MouseEventHandler(_screen_MouseMove);
            _screen.LostFocus += new RoutedEventHandler(_screen_LostFocus);
        }

        /*void _screen_MouseEnter(object sender, MouseEventArgs e)
        {
            IntPoint pos = new IntPoint();
            pos.X = (int)e.GetPosition(_screen).X;
            pos.Y = (int)e.GetPosition(_screen).Y;
            _active.MouseEnter(pos);
        }

        void _screen_MouseLeave(object sender, MouseEventArgs e)
        {
            this._leftMouseButtonState = false;

            IntPoint pos = new IntPoint();
            pos.X = (int)e.GetPosition(_screen).X;
            pos.Y = (int)e.GetPosition(_screen).Y;
            _active.MouseEnter(pos);
        }*/


        void _screen_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this._leftMouseButtonState = true;

            IntPoint pos = new IntPoint();
            pos.X = (int)e.GetPosition(_screen).X;
            pos.Y = (int)e.GetPosition(_screen).Y;
            _active.MouseLeftButtonDown(pos);
        }


        void _screen_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this._leftMouseButtonState = false;

            IntPoint pos = new IntPoint();
            pos.X = (int)e.GetPosition(_screen).X;
            pos.Y = (int)e.GetPosition(_screen).Y;
            _active.MouseLeftButtonUp(pos);
        }


        void _screen_MouseMove(object sender, MouseEventArgs e)
        {
            //only sends the mouse coordinates when the left mouse button is down-better performance
            if (this._leftMouseButtonState == true)
            {
                IntPoint pos = new IntPoint();
                pos.X = (int)e.GetPosition(_screen).X;
                pos.Y = (int)e.GetPosition(_screen).Y;
                
                //sends the position only when it's different
                if (_mouseMoveCache.X != pos.X || _mouseMoveCache.Y != pos.Y)
                {
                    _active.MouseMove(pos);
                    _mouseMoveCache = pos;
                }
            } 
        }


        /// <summary>
        ///     When the gamescreen loses focus we consider the left mouse button released.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _screen_LostFocus(object sender, RoutedEventArgs e)
        {
            this._leftMouseButtonState = false;
        }


        /// <summary>
        ///     Stops the gameloop when focus is lost.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void RootVisual_LostFocus(object sender, RoutedEventArgs e)
        {
            this._leftMouseButtonState = false;

            //should stop the gameloop when focus is lost-does not work
            //CompositionTarget.Rendering -= new EventHandler(GameLoop);
            _renderingEventHooked = false;
        }


        /// <summary>
        ///     Resumes the gameloop when we get focus.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void RootVisual_GotFocus(object sender, RoutedEventArgs e)
        {
            this._previousTime = new TimeSpan(0);
            this._currentTime = new TimeSpan(0);
            this._elapsedTime = new TimeSpan(0);


            //should resume the gameloop when focus is regained-does not work
           /* if (_renderingEventHooked == false)
            {
                CompositionTarget.Rendering += new EventHandler(GameLoop);
                _renderingEventHooked = true;
            }*/
        }

        
        /// <summary>
        ///     Detaches the active grid from the engine. Stops the gameloop.
        /// </summary>
        public void DetachActiveGrid()
        {
            //stops the gameloop
            _state = EngineState.stopped;

            //unhooks the mouse events
            //_screen.MouseEnter -= new MouseEventHandler(_screen_MouseEnter);
            //_screen.MouseLeave -= new MouseEventHandler(_screen_MouseLeave);
            _screen.MouseLeftButtonDown -= new MouseButtonEventHandler(_screen_MouseLeftButtonDown);
            _screen.MouseLeftButtonUp -= new MouseButtonEventHandler(_screen_MouseLeftButtonUp);
            _screen.MouseMove -= new MouseEventHandler(_screen_MouseMove);

            _active = null;
        }
    }
}
